package base

import (
	"bytes"
	"errors"
	"fmt"
	"math"
	"reflect"
	"strings"

	"github.com/jinzhu/gorm"
	//"github.com/labstack/gommon/log"
)

//RelationSign 查询语句间的关系符
type RelationSign string

const (
	//AND 与关系
	AND RelationSign = "AND"
	//OR 或关系
	OR RelationSign = "OR"
)

func (r RelationSign) String() string {
	switch r {
	case AND:
		return "AND"
	case OR:
		return "OR"
	default:
		return "AND"
	}
}

//OrderDirection 排序方向
type OrderDirection string

const (
	//ASC 顺序
	ASC OrderDirection = "ASC"
	//DESC 逆序
	DESC OrderDirection = "DESC"
)

func (o OrderDirection) String() string {
	switch o {
	case ASC:
		return "ASC"
	case DESC:
		return "DESC"
	default:
		return "ASC"
	}
}

//WhereItem 查询项目
type WhereItem struct {
	FieldName string
	Operator  string
	//ColumnValues 字段值 多个值以逗号隔开
	FieldValues string
}

//WhereCause 查询语句
type WhereCause struct {
	WhereItems   []WhereItem
	RelationSign RelationSign
}

//OrderItem 排序项目
type OrderItem struct {
	FieldName string
	Direction OrderDirection
}

//ListData 列表数据
//Objectlist 对象列表
//count 数量
type ListData struct {
	Objectlist interface{}
	Count      int
}

//Dao CRUD接口
type Dao interface {
	GetEntity() interface{}
	GetEntitis() interface{}
	GetScope() *gorm.Scope
	Find([]WhereCause, []OrderItem, int, int, int, ...string) (*ListData, error)
	Find2(string, []interface{}, []OrderItem, int, int) (*ListData, error)
	FindAll(orderItems []OrderItem, where string) (interface{}, error)
	Add(interface{}) (map[string]interface{}, error)
	Update(interface{}) (int64, error)
	Delete(interface{}) (int64, error)
	Deletes(vals ...interface{}) (int64, error)
}

//EntityFunc 返回Dao对象的方法类型
type EntityFunc func() interface{}

//EntitiesFunc 返回Dao对象数组方法类型
type EntitiesFunc func() interface{}

//DefaultDao 默认的Dao实现
type DefaultDao struct {
	//EntityName 实体类名称
	Entity       EntityFunc
	Entities     EntitiesFunc
	Scope        *gorm.Scope
	Relationship []string
	Fileter      DataFilter
}

//NewDefaultDao 新建DefaultDao
func NewDefaultDao(entity EntityFunc, entities EntitiesFunc, relationship []string) *DefaultDao {
	dao := new(DefaultDao)
	dao.Entity = entity
	dao.Entities = entities
	dao.Relationship = relationship
	dao.Scope = DB.NewScope(entity())
	return dao
}

//GetEntity 获取实体类
func (d DefaultDao) GetEntity() interface{} {
	return d.Entity()
}

//GetEntitis 获取实体数组
func (d DefaultDao) GetEntitis() interface{} {
	return d.Entities()
}

//GetScope 获取实体类的Scope定义
func (d DefaultDao) GetScope() *gorm.Scope {
	return d.Scope
}

//Find 获取列表数据
//whereItems 查询项目
//orderItems 排序项目
//pageNumber 数据分页码，0为查找所有数据
//ListData 返回列表数据
func (d DefaultDao) Find(whereCauses []WhereCause, orderItems []OrderItem, pageNumber int, lineCount int, userID int, pTypes ...string) (list *ListData, err error) {
	var whereBuf bytes.Buffer
	paramVals := make([]interface{}, 0, 10)
	causes2, spItems := d.classifyWhereCauses(whereCauses)
	for i, cause := range causes2 {
		whereBuf.WriteString("(")
		for j, item := range cause.WhereItems {
			itemWhere, vals, err := d.parseWhereItem(item)
			if err != nil {
				return nil, err
			}
			whereBuf.WriteString(itemWhere)
			if l := len(cause.WhereItems); j < l-1 {
				whereBuf.WriteString(" ")
				whereBuf.WriteString(cause.RelationSign.String())
				whereBuf.WriteString(" ")
			}
			for _, val := range vals {
				paramVals = append(paramVals, val)
			}
		}
		whereBuf.WriteString(")")
		if l := len(causes2); i < l-1 {
			whereBuf.WriteString(" AND ")
		}
	}
	ptLen := len(pTypes)
	mpt := "1"
	if ptLen == 0 {
		pTypes = append(pTypes, "1")
	}
	if d.Fileter != nil {
		if IsInSlice(pTypes, "4") {
			mpt = "4"
		} else if IsInSlice(pTypes, "O") {
			mpt = "O"
		} else if IsInSlice(pTypes, "G") {
			mpt = "G"
		} else if IsInSlice(pTypes, "L") {
			mpt = "L"
		} else if IsInSlice(pTypes, "J") {
			mpt = "J"
		} else if IsInSlice(pTypes, "3") {
			mpt = "3"
		}
		if mpt != "4" {
			var flowType string
			for _, item := range spItems {
				if item.FieldName == "#flowType" {
					flowType = item.FieldValues
					continue
				}
			}
			if flowType != "" {
				d.Fileter.SetAdditional("flowType", flowType)
			}
			sqlStr, err := d.Fileter.GetFilterKeys(mpt, userID)
			if err != nil {
				return nil, err
			}
			colName, err := d.Fileter.GetFilterColName()
			if err != nil {
				return nil, err
			}
			if l := len(causes2); l > 0 {
				whereBuf.WriteString(" AND ")
			}
			if len(sqlStr) == 0 {
				whereBuf.WriteString("1<>1")
			} else {
				whereBuf.WriteString(colName)
				whereBuf.WriteString(" in ( ")
				whereBuf.WriteString(sqlStr)
				whereBuf.WriteString(" ) ")
			}
		}
	}
	return d.Find2(whereBuf.String(), paramVals, orderItems, pageNumber, lineCount)
}

func (d DefaultDao) classifyWhereCauses(causes []WhereCause) (causes2 []WhereCause, specialItems []WhereItem) {
	causes2 = make([]WhereCause, 0, len(causes))
	specialItems = make([]WhereItem, 0, 5)
	i := 0
	for _, cause := range causes {
		if len(causes2) < i+1 {
			wc := WhereCause{
				WhereItems:   make([]WhereItem, 0, len(cause.WhereItems)),
				RelationSign: cause.RelationSign,
			}
			causes2 = append(causes2, wc)
			i++
		}
		for _, item := range cause.WhereItems {
			if strings.HasPrefix(item.FieldName, "#") {
				specialItems = append(specialItems, item)
			} else {
				causes2[i-1].WhereItems = append(causes2[i-1].WhereItems, item)
			}
		}
		if len(causes2[i-1].WhereItems) == 0 {
			causes2 = causes2[:i-1]
			i--
		}
	}
	return
}

//parseWhereItem 解析WhereItem
func (d DefaultDao) parseWhereItem(item WhereItem) (string, []interface{}, error) {
	cloName, err := d.getColumnName(item.FieldName)
	if err != nil {
		return "", nil, err
	}
	var buf bytes.Buffer
	buf.WriteString(cloName)
	itemVals, err := d.parseValue(item.FieldName, item.FieldValues)
	if err != nil {
		return "", nil, err
	}
	vals := make([]interface{}, 0, 10)
	switch item.Operator {
	case "like":
		buf.WriteString(" like ?")
		vals = append(vals, "%"+itemVals+"%")
	case "leftlike":
		buf.WriteString(" like ?")
		vals = append(vals, itemVals+"%")
	case "rightlike":
		buf.WriteString(" like ?")
		vals = append(vals, "%"+itemVals)
	case "notlike":
		buf.WriteString(" not like ?")
		vals = append(vals, "%"+itemVals+"%")
	case "in":
		buf.WriteString(" in (")
		items := strings.Split(itemVals, ",")
		for _, item := range items {
			buf.WriteString("?,")
			vals = append(vals, item)
		}
		if l := buf.Len(); l > 0 {
			buf.Truncate(l - 1)
		}
		buf.WriteString(")")
	case "notin":
		buf.WriteString(" not in (")
		items := strings.Split(itemVals, ",")
		for _, item := range items {
			buf.WriteString("?,")
			vals = append(vals, item)
		}
		if l := buf.Len(); l > 0 {
			buf.Truncate(l - 1)
		}
		buf.WriteString(")")
	case "between":
		buf.WriteString(" between ? and ?")
		vals2 := strings.Split(itemVals, ",")
		if l := len(vals2); l < 2 {
			errInfo := fmt.Sprintf("列%s的段值参数必须提供以逗号隔开的两个值！", item.FieldName)
			return "", nil, errors.New(errInfo)
		}
		vals = append(vals, vals2[0])
		vals = append(vals, vals2[1])
	case "null":
		buf.WriteString(" is null ")
	case "empty":
		buf.WriteString(" in (null,?) ")
		vals = append(vals, "")
	default:
		buf.WriteString(" ")
		buf.WriteString(item.Operator)
		buf.WriteString(" ? ")
		vals = append(vals, itemVals)
	}
	return buf.String(), vals, nil
}

//Find2 获取列表数据
//whereCauses where语句，参数值用？做占位符,字段名必须为数据库表中的字段名
//vals 参数值
//pageNumber 数据分页码，0为查找所有数据
//ListData 返回列表数据
func (d DefaultDao) Find2(whereCauses string, vals []interface{}, orderItems []OrderItem, pageNumber int, lineCount int) (list *ListData, err error) {
	var count int
	count, err = d.count(whereCauses, vals)
	if err != nil {
		return nil, err
	}
	objs := d.Entities()
	order, err := d.parseOrderItems(orderItems)
	if err != nil {
		return nil, err
	}
	db := DB.Where(whereCauses, vals...).Order(order)
	if pageNumber == 0 || lineCount == 0 {
		if d.Relationship != nil {
			for _, fName := range d.Relationship {
				db = db.Preload(fName)
			}
		}
		err = db.Find(objs).Error
	} else {
		pageCount := math.Ceil(float64(count) / float64(lineCount))
		if float64(pageNumber) > pageCount && pageCount != 0 {
			pageNumber = int(pageCount)
		}
		limit := lineCount
		offset := (pageNumber - 1) * limit
		db = db.Offset(offset).Limit(limit)
		if d.Relationship != nil {
			for _, fName := range d.Relationship {
				db = db.Preload(fName)
			}
		}
		err = db.Find(objs).Error
	}
	if err != nil {
		return nil, err
	}
	list = &ListData{
		Objectlist: objs,
		Count:      count,
	}
	return
}
func (d DefaultDao) parseOrderItems(orderItems []OrderItem) (string, error) {
	if orderItems == nil {
		orderItems = make([]OrderItem, 0, 1)
	}
	if l := len(orderItems); l == 0 {
		field := d.Scope.PrimaryField()
		if field == nil {
			field = d.Scope.Fields()[0]
		}
		oi := OrderItem{
			FieldName: field.Name,
			Direction: "ASC",
		}
		orderItems = append(orderItems, oi)
	}
	var orderBuf bytes.Buffer
	for _, item := range orderItems {
		cloName, err := d.getColumnName(item.FieldName)
		if err != nil {
			return "", err
		}
		orderBuf.WriteString(cloName)
		orderBuf.WriteString(" ")
		orderBuf.WriteString(item.Direction.String())
		orderBuf.WriteString(",")
	}
	if l := orderBuf.Len(); l > 0 {
		orderBuf.Truncate(l - 1)
	}
	return orderBuf.String(), nil
}
func (d DefaultDao) getColumnName(fieldName string) (string, error) {
	names := strings.Split(fieldName, ".")
	field, ok := d.Scope.FieldByName(names[0])
	if l := len(names); l == 1 {
		var colName string
		if ok {
			colName = field.DBName
		} else {
			colName = fieldName
		}
		return colName, nil
	} else if l == 2 {
		if !ok {
			info := fmt.Sprintf("结构体：%s字段名：%s没有找到!", reflect.TypeOf(d.Entity()), names[0])
			return "", errors.New(info)
		}
		if field.IsForeignKey {
			return d.getColumnName(field.Name)
		}
		if field.Relationship == nil {
			info := fmt.Sprintf("结构体：%s的字段：%s没有定义外键tag！", reflect.TypeOf(d.Entity()), names[0])
			return "", errors.New(info)
		}
		foreigns := field.Relationship.ForeignDBNames
		return foreigns[0], nil
	} else {
		return "", errors.New("不支持三层及以上的嵌套字段！")
	}
}
func (d DefaultDao) parseValue(fieldName string, vals string) (string, error) {
	names := strings.Split(fieldName, ".")
	if l := len(names); l == 1 {
		return vals, nil
	} else if l == 2 {
		field, _ := d.Scope.FieldByName(names[0])
		sName := field.Struct.Type.Name()
		dao, err := GetDao(sName)
		if err != nil {
			return "", err
		}
		dDao, ok := dao.(*DefaultDao)
		if !ok {
			dao2 := reflect.ValueOf(dao).Elem().FieldByName("DefaultDao").Interface()
			dao3, ok := dao2.(DefaultDao)
			if !ok {
				info := fmt.Sprintf("dao：%s必须是DefaultDao,或者嵌入了DefaultDao!", reflect.TypeOf(dao))
				return "", errors.New(info)
			}
			dDao = &dao3
		}
		if err != nil {
			return "", err
		}
		colName, err := dDao.getColumnName(names[1])
		if err != nil {
			return "", err
		}
		pkName := dDao.Scope.PrimaryKey()
		where := fmt.Sprintf("%s in (?)", colName)
		rows, err := DB.Table(dDao.Scope.TableName()).Where(where, strings.Split(vals, ",")).Select(pkName).Rows()
		if err != nil {
			return "", err
		}
		retVals := make([]string, 0, 1)
		var retVal string
		for rows.Next() {
			rows.Scan(&retVal)
			retVals = append(retVals, retVal)
		}
		return strings.Join(retVals, ","), nil
	}
	return "", errors.New("不支持三层以上的嵌套字段！")
}

func (d DefaultDao) count(whereCauses string, vals []interface{}) (int, error) {
	var count int
	db := DB.Table(d.Scope.TableName())
	db = db.Where(whereCauses, vals...)
	err := db.Count(&count).Error
	if err != nil {
		return 0, err
	}
	return count, nil
}

//FindAll 获取所有
func (d DefaultDao) FindAll(orderItems []OrderItem, where string) (list interface{}, err error) {
	var orderBuf bytes.Buffer
	for _, item := range orderItems {
		orderBuf.WriteString(item.FieldName)
		orderBuf.WriteString(" ")
		orderBuf.WriteString(item.Direction.String())
		orderBuf.WriteString(",")
	}
	if l := orderBuf.Len(); l > 0 {
		orderBuf.Truncate(l - 1)
	}
	orderStr := orderBuf.String()
	db := DB
	if where != "" {
		db = db.Where(where)
	}
	if orderStr != "" {
		db = db.Order(orderStr)
	}
	objs := d.Entities()
	if d.Relationship != nil {
		for _, fName := range d.Relationship {
			db = db.Preload(fName)
		}
	}
	err = db.Find(objs).Error
	if err != nil {
		return nil, err
	}
	list = objs
	return
}

//Add 添加数据
//obj 要保存的对象
//id 对象的主键
//返回主键的切片值
func (d DefaultDao) Add(obj interface{}) (map[string]interface{}, error) {
	db := DB.Set("gorm:save_associations", false).Create(obj)
	err := db.Error
	if err != nil {
		return nil, err
	}
	fields := d.Scope.PrimaryFields()
	retVals := make(map[string]interface{})
	for _, field := range fields {
		f := reflect.ValueOf(obj).Elem().FieldByName(field.Name)
		retVals[field.Name] = f.Interface()
	}
	return retVals, nil
}

//Update 更新数据
//obj 要保存的对象
//lineCount 影响行数
func (d DefaultDao) Update(obj interface{}) (lineCount int64, err error) {
	db := DB.Set("gorm:save_associations", false).Save(obj)
	err = db.Error
	lineCount = db.RowsAffected
	if err != nil {
		return 0, err
	}
	return
}

//Delete 删除数据
//obj 要保存的对象
//lineCount 影响行数
func (d DefaultDao) Delete(obj interface{}) (lineCount int64, err error) {
	entity := d.Entity()
	field := d.Scope.PrimaryField()
	f := reflect.ValueOf(obj).Elem().FieldByName(field.Name)
	if IsBlank(f) {
		info := fmt.Sprintf("为结构体%s的提供的主键%s的值%s无效！", reflect.TypeOf(entity), field.Name, f.Interface())
		return 0, errors.New(info)
	}
	db := DB.Delete(obj)
	err = db.Error
	lineCount = db.RowsAffected
	if err != nil {
		return 0, err
	}
	return
}

//Deletes 按照指定的主键值ids删除多行数据(仅支持单主键表)
//vals 参数值
//lineCount 影响行数
func (d DefaultDao) Deletes(vals ...interface{}) (lineCount int64, err error) {
	obj := d.Entity()
	field := d.Scope.PrimaryField()
	var buf bytes.Buffer
	buf.WriteString(field.DBName)
	buf.WriteString(" in (?)")
	db := DB.Delete(obj, buf.String(), vals)
	err = db.Error
	lineCount = db.RowsAffected
	if err != nil {
		return 0, err
	}
	return
}

//DaoPool Dao对象池
type DaoPool struct {
	daoMap map[string]Dao
}

//Get 从Dao对象池中获取
func (p *DaoPool) Get(key string) (Dao, error) {
	if dao, find := p.daoMap[key]; find {
		return dao, nil
	}
	errInfo := fmt.Sprintf("entity名称%s在Dao对象池中不存在!", key)
	return nil, errors.New(errInfo)
}

//Put 向Dao对象池中添加对象
func (p *DaoPool) Put(key string, dao Dao) error {
	if _, find := p.daoMap[key]; find {
		errInfo := fmt.Sprintf("entity名称%s已经存在!", key)
		return errors.New(errInfo)
	}
	p.daoMap[key] = dao
	return nil
}

//DaoPoolObj 数据池对象
var DaoPoolObj *DaoPool

//GetDao 创建Dao对象
func GetDao(entityName string) (Dao, error) {
	dao, err := DaoPoolObj.Get(entityName)
	if err != nil {
		return nil, err
	}
	return dao, nil
}
func init() {
	DaoPoolObj = new(DaoPool)
	if DaoPoolObj.daoMap == nil {
		DaoPoolObj.daoMap = make(map[string]Dao, 0)
	}
}
